package com.googlecode.lohr.parser.combinators.test;

import static com.googlecode.lohr.parser.combinators.Parsers.cho;
import static com.googlecode.lohr.parser.combinators.Parsers.choice;
import static com.googlecode.lohr.parser.combinators.Parsers.longest;
import static com.googlecode.lohr.parser.combinators.Parsers.lcho;
import static com.googlecode.lohr.parser.combinators.Parsers.max;
import static com.googlecode.lohr.parser.combinators.Parsers.opt;
import static com.googlecode.lohr.parser.combinators.Parsers.range;
import static com.googlecode.lohr.parser.combinators.Parsers.rep;
import static com.googlecode.lohr.parser.combinators.Parsers.seq;
import static com.googlecode.lohr.parser.combinators.Parsers.str;

import java.util.HashMap;

import com.googlecode.lohr.parser.combinators.MatchError;
import com.googlecode.lohr.parser.combinators.matcher.MatchContext;
import com.googlecode.lohr.parser.combinators.matcher.Matcher;
import com.googlecode.lohr.parser.combinators.matcher.NamedMatcher;
import com.googlecode.lohr.parser.combinators.matcher.common.BooleanMatcher;
import com.googlecode.lohr.parser.combinators.matcher.common.DecimalMatcher;
import com.googlecode.lohr.parser.combinators.matcher.common.DoubleMatcher;
import com.googlecode.lohr.parser.combinators.matcher.common.IntegerMatcher;
import com.googlecode.lohr.parser.combinators.matcher.common.NameMatcher;
import com.googlecode.lohr.parser.combinators.matcher.common.QuotedStringMatcher;
import com.googlecode.lohr.parser.combinators.matcher.common.SimpleBlockMatcher;
import com.googlecode.lohr.parser.combinators.matcher.common.SingleLineCommentMatcher;
import com.googlecode.lohr.parser.combinators.matcher.common.WhitespaceMatcher;
import com.googlecode.lohr.parser.combinators.node.StringNode;

/**
 * A grammar for the Meteor Description Language.
 * 
 * @author Ted Stockwell
 */
public class MeteorDescriptionLanguageGrammar
{
	/**
	 * Symbols used in this grammar
	 */
	public static final String DOCUMENT = "meteorDocument";
	public static final String STATEMENT = "statement";
	public static final String DIRECTIVE = "directive";
	public static final String BLOCK = "block";
	public static final String TRIPLE = "triple";
	public static final String PREDICATE_VALUES = "predicateValues";
	public static final String VERB = "verb";
	public static final String OBJECT_LIST = "objectList";
	public static final String OBJECT = "object";
	public static final String LITERAL = "literal";
	public static final String LANGUAGE = "language";
	public static final String DATATYPE_STRING = "datatypeString";
	public static final String INTEGER = "integer";
	public static final String DOUBLE = "double";
	public static final String DECIMAL = "decimal";
	public static final String BOOL = "bool";
	public static final String IMPORT = "import";
	public static final String BASE = "base";
	public static final String SUBJECT = "subject";
	public static final String RESOURCE = "resource";
	public static final String URIREF = "uriref";
	public static final String PREFIX_NAME = "prefixName";
	public static final String NODE_ID = "nodeID";
	public static final String QNAME = "qname";
	public static final String QUOTED_STRING = "quotedString";
	public static final String BLOCK_TYPE = "blockType";
	public static final String BLOCK_BODY = "blockBody";
	
	static final String UTF8= "UTF-8";

	public static class URIRefMatcher extends Matcher 
	{
		@Override 
		public void match(MatchContext ctx, int start)
		{
			final String input= ctx.getInput();
			if (input.length() <= start || input.charAt(start) != '<') {
				ctx.matchComplete(this, start, new MatchError("Expected '<'", start));
				return;
			}
			
			int pos = input.indexOf('>', start);
			if (pos < 0) {
				ctx.matchComplete(this, start, new MatchError("End of URI reference not found", start));
				return;
			}
			
			pos++;
			StringNode node= new StringNode(this, start, pos, input.substring(start, pos));
			ctx.matchFound(this, start, node);
			ctx.matchComplete(this, start);
		}
		
		@Override
		public String getLabel()
		{
			return "URI reference";
		}
	}
	
	static HashMap<String, Matcher> __matchersById= new HashMap<String, Matcher>(); 
	
	
	
	static {
		Matcher lower = range('a', 'z');
		Matcher digit = range('0', '9');
		Matcher singleLineComment= defineMatcher("SINGLE_LINE_COMMENT", new SingleLineCommentMatcher("//"));
		Matcher multiLineComment= defineMatcher("MULTI_LINE_COMMENT", new SimpleBlockMatcher("/*", "*/"));
		Matcher ws = rep(1, max, choice(new WhitespaceMatcher(), singleLineComment, multiLineComment));
		Matcher optws = opt(ws);
		Matcher name= new NameMatcher();
		
		NamedMatcher quotedString= defineMatcher(QUOTED_STRING, new QuotedStringMatcher());
		NamedMatcher nodeID= defineMatcher(NODE_ID, seq(str("_:"), name));
		NamedMatcher prefixName= defineMatcher(PREFIX_NAME, name);
		NamedMatcher qname= defineMatcher(QNAME, cho( seq(prefixName,str(":"),name), seq(str(":"),name), str(":")));
		NamedMatcher uriref= defineMatcher(URIREF, longest(new URIRefMatcher()));
		NamedMatcher resource= defineMatcher(RESOURCE, cho(name, uriref, qname));
		NamedMatcher subject= defineMatcher(SUBJECT, cho(resource, nodeID, str("[]")));
		NamedMatcher base= defineMatcher(BASE, seq(str("@base"), ws, uriref));
		NamedMatcher importt= defineMatcher(IMPORT, seq(str("@import"), ws, opt(seq(prefixName, str(":"), ws)), uriref));
		NamedMatcher bool= defineMatcher(BOOL, new BooleanMatcher());
		NamedMatcher decimal= defineMatcher(DECIMAL, new DecimalMatcher());
		NamedMatcher doubl= defineMatcher(DOUBLE, new DoubleMatcher());
		NamedMatcher integer= defineMatcher(INTEGER, new IntegerMatcher());
		NamedMatcher datatypeString= defineMatcher(DATATYPE_STRING, seq(quotedString, str("^^"), resource));
		NamedMatcher language= defineMatcher(LANGUAGE, seq(rep(1, max, lower), rep(0, max, seq(str("-"), rep(1, max, seq(cho(lower, digit)))))));
		NamedMatcher literal= defineMatcher(LITERAL, cho(seq(quotedString, opt(seq(str("@"), language))), datatypeString, integer, doubl, decimal, bool));
		NamedMatcher object= defineMatcher(OBJECT, cho(resource, literal, nodeID, str("[]")));
		NamedMatcher objectList= defineMatcher(OBJECT_LIST, seq(object, rep(0, max, seq(optws, str(","), optws, object))));
		NamedMatcher verb= defineMatcher(VERB, choice(resource, str("a")));
		NamedMatcher propertyValuePair= defineMatcher(PREDICATE_VALUES, seq(verb, ws, objectList));
		NamedMatcher triple= defineMatcher(TRIPLE, seq(subject, ws, propertyValuePair, optws, str(";")));
		NamedMatcher directive= defineMatcher(DIRECTIVE, seq(cho(importt, base), optws, str(";")));
		
		NamedMatcher blockType= defineMatcher(BLOCK_TYPE, lcho( seq(resource, ws, resource), seq(str("@external"), ws, resource), resource));
		NamedMatcher blockBody= defineMatcher(BLOCK_BODY, rep(1, max, seq(propertyValuePair, optws, str(";"))));
		NamedMatcher block= defineMatcher(BLOCK, seq(blockType, optws, str("{"), optws, blockBody, optws, str("}")));
		blockBody.define(seq(cho(block, blockBody.getDefinition()), rep(0, max, seq(optws, cho(block, blockBody.getDefinition()))))); // blocks can be nested 
		
		NamedMatcher statement= defineMatcher(STATEMENT, seq(cho(directive, triple, block)));
		defineMatcher(DOCUMENT, seq(optws, longest(rep(1, max, seq(statement, optws)))));
	}
	
	private static NamedMatcher defineMatcher(String symbol, Matcher definition) {
		NamedMatcher matcher= new NamedMatcher(symbol).define(definition);
		__matchersById.put(symbol, matcher);
		return matcher;
	}
	
	public static Matcher getMatcher(String symbol) {
		return __matchersById.get(symbol);
	}
	public static Matcher getMatcher() {
		return getMatcher(DOCUMENT);
	}
	
	
}
